/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.etherframework.behaviour.rules.util
{
	import flash.events.IEventDispatcher;
	import flash.utils.Dictionary;
	
	import mx.core.mx_internal;
	import mx.events.PropertyChangeEvent;
	
	import org.etherframework.behaviour.rules.context.IContext;
	import org.etherframework.behaviour.rules.core.Descriptor;
	import org.etherframework.behaviour.rules.core.IContextHolder;
	import org.etherframework.behaviour.rules.core.IControl;
	
	[ExcludeClass]
	public class ContextResolver
	{
		private static const CONTEXT:String = "context";
		
		private static const LAST_CONTEXT:String = "lastContext";
		
		private static const LOCAL_CONTEXT:String = "localContext";
		
		private static const REASON:String = "reason";
		
		private static var cache:Dictionary = new Dictionary(true);
		
		private static var bindings:Dictionary = new Dictionary();
		
		public static function isBindable(descriptor:XML):Boolean
		{
			return descriptor.variable.(@name == "_bindings" && @uri == "http://www.adobe.com/2006/flex/mx/internal").length() > 0;
		}
		
		public static function register(holder:IContextHolder, contextClosure:Function, 
										lastContextClosure:Function, localContextClosure:Function, reasonClosure:Function):Boolean
		{
			if (!holder || cache[holder])
				return false;
			
			cache[holder] = new Info(contextClosure, lastContextClosure, localContextClosure, reasonClosure);
			
			return true;
		}
		
		public static function unregister(holder:IContextHolder):Boolean
		{
			if (cache[holder])
				return (delete cache[holder]);
			
			return false;
		}
		
		public static function resolve(control:IControl, useBindings:Boolean = true):void
		{
			if (!(control || cache[control.descriptor.document]))
				return;
			
			var bindings:Array = null;
			var evt:PropertyChangeEvent;
			var descriptor:Descriptor = control.descriptor;
			var doc:IContextHolder = descriptor.document;
			var info:Info = cache[doc];
			var controlContext:IContext = control.context;
			var lastContext:IContext = controlContext.parent ? (controlContext.parent.lastChild || controlContext.parent) : null;
			var context:IContext = descriptor.owner.context;
			var reason:* = controlContext.source.reason.value;
			
			forceClosure(control, info, doc, CONTEXT, doc.context, context);
			forceClosure(control, info, doc, LAST_CONTEXT, doc.lastContext, lastContext);
			forceClosure(control, info, doc, LOCAL_CONTEXT, doc.localContext, controlContext);
			forceClosure(control, info, doc, REASON, doc.reason, reason);
			
			if(control is IContextHolder && cache[control])
			{
				info = cache[control];
				info.localContext(controlContext);
				info.lastContext(lastContext);
				info.reason(reason);
			}
			
			if (useBindings && descriptor.uid)
			{
				bindings = getBindingsById(descriptor.uid, doc.descriptor.type, doc);
				// just refresh
				enableBindings(bindings, true);
				enableBindings(bindings, false);
			}
		}
		
		public static function enableBindings(bindings:Array, value:Boolean):void
		{
			if (bindings)
			{
				for (var i:int = 0; i < bindings.length; ++i)
				{
					if (bindings[i].mx_internal::isEnabled != value)
						bindings[i].mx_internal::isEnabled = value;
				}
			}
		}
		
		public static function enableBindingsByObject(owner:Object, descriptor:XML, value:Boolean):void
		{
			enableBindings(getBindings(owner, descriptor), value);
		}
		
		public static function getBindings(owner:Object, descriptor:XML):Array
		{
			if (descriptor.variable.(@name == "_bindings").length())
			{
				return owner.mx_internal::_bindings;
			}
			
			return null;
		}
		
		private static function forceClosure(control:IControl, info:Info, doc:IEventDispatcher, source:String, oldValue:Object, newValue:Object):void
		{
			// FIXME:
			if (!info.dictionary[control])
				info.dictionary[control] = new Dictionary();
			
			newValue = info.dictionary[control][source] || newValue;
			
			if (oldValue === newValue)
				return;
			
			if (info[source])
			{
				info.dictionary[control][source] = newValue;
				info[source](newValue);
				doc.dispatchEvent(PropertyChangeEvent.createUpdateEvent(doc, source, oldValue, newValue));
			}
		}
		
		private static function bindingsContains(bindings:Array, src:String):Boolean
		{
			if (bindings)
			{
				for (var i:int = 0; i < bindings.length; ++i)
				{
					if (bindings[i].mx_internal::srcString == src)
						return true;
				}
			}
			
			return false;
		}
		
		private static function getBindingsById(id:String, descriptor:XML, owner:Object):Array
		{
			if (id && bindings[id])
				return bindings[id];
			
			var result:Array = [];
			var binding:Object;
			
			if (descriptor.variable.(@name == "_bindingsByDestination").length())
			{
				binding = owner.mx_internal::_bindingsByDestination;
				
				for (var i:String in binding)
				{
					if (i.indexOf(id + ".") == 0)
						result.push(binding[i]);
				}
			}
			
			bindings[id] = result;
			
			return result;
		}
	}
}
import org.etherframework.behaviour.rules.context.IContext;

import flash.utils.Dictionary;

final class Info
{
	public var context:Function;
	
	public var lastContext:Function;
	
	public var localContext:Function;
	
	public var reason:Function;
	
	public var dictionary:Dictionary = new Dictionary(true);
	
	public function Info(context:Function, lastContext:Function, localContext:Function, reason:Function)
	{
		this.context = context;
		this.lastContext = lastContext;
		this.localContext = localContext;
		this.reason = reason;
	}
}